package org.zoomdev.zoom.dao.configs;

import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.dao.SqlBuilder;
import org.zoomdev.zoom.dao.SqlConfig;
import org.zoomdev.zoom.dao.adapters.ResultSetMapping;
import org.zoomdev.zoom.dao.driver.SqlDriver;
import org.zoomdev.zoom.dao.entity.Entity;
import org.zoomdev.zoom.dao.entity.Field2ColumnMappingConfig;
import org.zoomdev.zoom.dao.meta.JoinMeta;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static org.zoomdev.zoom.dao.SqlBuilder.AND;
import static org.zoomdev.zoom.dao.SqlBuilder.SPACE;

public final class Query {
    private final StringBuilder having = new StringBuilder(32);
    private final List<Field2ColumnMappingConfig> fields = new ArrayList<>(16);
    private final SqlDriver driver;
    private final StringBuilder select = new StringBuilder(256);
    private final StringBuilder orderBy = new StringBuilder(32);
    private final StringBuilder join = new StringBuilder(64);
    private final StringBuilder groupBy = new StringBuilder(32);

    private String table;
    private List<JoinMeta> joinMetas = new ArrayList<>(3);
    private int position;
    private int limit;
    private ResultSetMapping mapping;

    public Query(SqlDriver driver) {
        this.driver = driver;
    }

    public List<Field2ColumnMappingConfig> getFields() {
        return fields;
    }

    public void setLimit(int position, int limit) {
        this.position = position;
        this.limit = limit;
    }

    public void clear() {
        this.limit = 0;
        this.mapping = null;
        this.select.setLength(0);
        this.fields.clear();
    }

    public String build(SqlConfig cfg, Where where) {
        StringBuilder sql = cfg.getSqlBuilder();
        sql.append("SELECT");
        if (select.length() == 0) {
            sql.append(" *");
        } else {
            sql.append(" ").append(select);
        }
        sql.append(" FROM ");

        driver.protectTable(sql, cfg.getTable());

        sql.append(join);

        where.append(sql);

        sql.append(groupBy);
        if (having.length() > 0) {
            sql.append(" HAVING ").append(having);
        }

        sql.append(orderBy);

        if (limit > 0) {
            sql.append(" LIMIT ?,?");
        }
        return sql.toString();
    }

    public int getPosition() {
        return position;
    }

    public int getLimit() {
        return limit;
    }

    public String getTable() {
        return table;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public void selectFunc(String field) {
        if (select.length() > 0) {
            select.append(SqlBuilder.COMMA);
        }
        select.append(field);
    }

    public void setParameter(Where where, PreparedStatement stmt) throws SQLException {
        int index = where.setParameter(stmt, 1);
        if (limit > 0) {
            stmt.setInt(index, position);
            stmt.setInt(index + 1, limit);
        }
    }

    public List<Object> getParameterValues(Where where) {
        List<Object> whereValues = where.getValues();
        List<Object> data = new ArrayList<>(limit > 0 ? whereValues.size() + 2 : whereValues.size());
        if (!whereValues.isEmpty()) {
            data.addAll(whereValues);
        }
        if (limit > 0) {
            data.add(position);
            data.add(limit);
        }
        return data;
    }

    public ResultSetMapping getMapping() {
        return mapping;
    }

    public void setMapping(ResultSetMapping mapping) {
        this.mapping = mapping;
    }

    public void orderBy(String field, SqlBuilder.Sort sort) {
        assert (sort != null && field != null);
        if (orderBy.length() != 0) {
            orderBy.append(',');
        } else {
            orderBy.append(" ORDER BY ");
        }
        orderBy.append(field).append(SPACE).append(sort.value());
    }

    public void groupBy(String group) {
        groupBy.append(" GROUP BY ").append(group);
    }

    public void having(Where where, String name, Symbol symbol, Object value) {
        where.whereImpl(having, name, symbol, value, AND);
    }

    public void join(String table, String on, String type) {
        if (type == null) {
            type = "INNER";
        }
        join.append(SPACE)
                .append(type)
                .append(" JOIN ");
        driver.protectTable(join, table).append(" ON ")
                .append(on);
    }

    public void prepare(SqlConfig cfg) {


        Query query = cfg.getQuery();
        Entity entity = cfg.getEntity();
        Filter<Field2ColumnMappingConfig> filter = cfg.getFilter();
        if (!query.hasSelect()) {
            Field2ColumnMappingConfig[] list = entity.getToFields();
            for (Field2ColumnMappingConfig field : list) {
                if (filter != null) {
                    if (filter.accept(field)) {
                        query.add(field);
                    }
                } else {
                    query.add(field);
                }
            }
        }
        if (cfg.getTable() == null) {
            cfg.setTable(entity.getTableName());
        }


    }

    public boolean hasSelect() {
        return select.length() > 0;
    }

    public void add(Field2ColumnMappingConfig field) {
        selectRaw(field.getColumnName());
        fields.add(field);
    }

    public void selectRaw(String field) {
        if (select.length() > 0) {
            select.append(SqlBuilder.COMMA);
        }
        driver.protectColumn(select, field);
    }
}

